package org.example.study7;

public class MyBlockingQueueProblematic {
    // 定义一个数组来存放数据，具体的容量由构造方法中的参数决定
    private Integer[] elementData;
    // 定义头尾下标
    private volatile int head = 0;
    private volatile int tail = 0;
    // 定义数组中元素的个数
    private volatile int size = 0;

    public MyBlockingQueueProblematic(int capacity) {
        if (capacity <= 0) {
            throw new RuntimeException("队列容量必须大于0.");
        }
        elementData = new Integer[capacity];
    }

    // 插入数据的方法
    public void put(Integer value) throws InterruptedException {
        synchronized (this) {
            // 判断队列是否已满
            if (size >= elementData.length) {
                // 阻塞队列在队列满的时候就应该阻塞等待
                // 等待
                this.wait();
            }
            // 插入数据的过程
            // 在队列尾部插入元素
            elementData[tail] = value;
            // 移动队尾下标
            tail++;
            // 处理队尾下标
            if (tail >= elementData.length) {
                tail = 0; // 回到数据头
            }
            // 修改size的值
            size++;
            // 唤醒阻塞线程
            this.notifyAll();
        }
    }

    // 获取数据的方法
    public synchronized Integer take() throws InterruptedException {
        // 判断队列是否为空
        if (size == 0) {
            this.wait();
        }
        // 出队的过程
        // 获取要出队的元素
        Integer value = elementData[head];
        // 移动队头下标
        head++;
        // 处理队头下标
        if (head >= elementData.length) {
            head = 0; // 回来数据头
        }
        // 处理数组中的元素个数
        size--;
        // 唤醒阻塞等待的线程
        this.notifyAll();
        // 返回元素
        return value;
    }
}

/*
问题本质：虚假唤醒（Spurious Wakeup）
Java 的 wait() 方法在以下情况可能被唤醒：
其他线程调用 notify()/notifyAll()。
虚假唤醒：没有明确通知时，JVM 也可能唤醒线程（虽然概率低，但需防御）。

使用 if 会导致线程在唤醒后不重新检查条件，直接执行后续操作，从而破坏线程安全。

*/



/*问题场景： 生产者线程（put 方法）
假设队列已满，生产者 A 调用 put()，进入 wait()。
消费者 B 取出一个元素，调用 notifyAll()，唤醒生产者 A。
在生产者 A 被唤醒到重新获取锁的间隙，另一个生产者 C 抢先插入数据，队列再次变满。
生产者 A 被唤醒后，由于使用 if 而非 while，不再检查队列是否满，直接执行插入操作，导致：

数据覆盖（tail 指针越界）。
size 计数错误（超过数组容量）。

*/

/*问题场景：消费者线程（take 方法）

队列为空，消费者 X 调用 take()，进入 wait()。
生产者 Y 插入一个元素，调用 notifyAll()，唤醒消费者 X。
在消费者 X 被唤醒到重新获取锁的间隙，另一个消费者 Z 抢先取走数据，队列再次变空。
消费者 X 被唤醒后，由于使用 if 而非 while，不再检查队列是否空，直接执行取出操作，导致：

读取到无效的 elementData[head]（可能为 null 或旧数据）。
head 指针越界。

*/


/*问题总结
问题类型	           现象	                                      原因
数据覆盖	     队列满时插入数据导致元素被覆盖	                 if 未防御虚假唤醒和竞争
越界访问	     head 或 tail 超出数组范围	                     唤醒后未重新检查条件
无效数据	   消费者从空队列中取出 null 或旧数据	                 线程唤醒后队列状态已变化
计数器错误	size 的值可能小于 0 或大于数组容量	             竞态条件下操作未受保护*/


/*
总结wait在条件语句中必须使用 while 循环而非 if，确保线程被唤醒后重新检查条件，防御以下问题：
1. 虚假唤醒（JVM 特性要求）。
2. 线程唤醒后队列状态被其他线程修改（竞态条件）。*/